#include "percep.h"

/***********************************************************************************
    Definimos el tipo atravez de la primer variable del constructor y la cantidad de entradas en la segunda
    El tipo es el equivalente a la funcion y se designara 0 para la funcion lineal, 1 para la funcion sigmoidea...
    Y se iran incorponrando mas...
***********************************************************************************/
perceptron::perceptron(int tip, int can, double factor)
{
    this->tipo = tip;
    this->cant_entradas = can;
    this->pesos = (double *)malloc(sizeof(double)*can);
    this->entradas = (double *)malloc(sizeof(double)*can);
    this->factor_aprendizaje = factor;
    this->iniciar_pesos();
}

perceptron::perceptron(char *archivo)
{
    cout<<"constructor con archivo :"<<archivo<<endl;
    cargar_red(archivo);

}

void perceptron::iniciar_pesos()
{
    for(int i= 0; i<this->cant_entradas;i++)
    {
        cout<<"Ingrese los pesos sinapticos"<<endl;
        cout<<"Peso "<<i<<" : ";
        cin>>this->pesos[i];
    }

}

perceptron::~perceptron()
{
    delete this->pesos;
    delete this->entradas;

}

/*****************************************************************
La funcion respuesta carga el vector pasado como parametro, como valores de las entradas
luego obtiene la salida de la neurona y devuelve dicho valor
*********************************************************************/

double perceptron::respuesta(double *entr)
{
    for(int i = 0; i< this->cant_entradas; i++)
    {
        this->entradas[i] = entr[i];
    }
    this->ejecutar();
    return salida;
}

/****************************************************************
* La funcion entrenar() entrena la red a apartir de la matriz pasada como parametro, o de una matriz guardada en un archivo
* En dicho archivo se encontraran las entradas alineadas y al final de la linea la salida esperada en la
* Siguiente linea la prox entrada y sucesivamente
 ***************************************************************/

void perceptron::entrenar(double **entrenamiento, int casos)
{
    int i, j, corregido = 1, pasadas = 0;
    double error;
    cout<<"Comenzando el entrenamiento :"<<endl;
    do
    {
        corregido = 0;
        cout<< "Pasada numero "<<pasadas<<endl;
        for(i = 0; i<casos; i++)
        {
            for(j = 0; j<this->cant_entradas;j++)
            {
                this->entradas[j] = entrenamiento[i][j];
            }
            this->ejecutar();
            error = entrenamiento[i][j] - this->salida;
            cout<<"Salida esperada: "<< entrenamiento[i][j]<<endl;
            cout<<"Error de la red: "<< error<<endl;
            if( error != 0 )
            {
                this->corregir_pesos(error);
                corregido ++;
            }
            /*else
            {
                cout<<"No se ha corregido nada"<<endl;
                corregido -= 1;
            }*/
        }
        pasadas++;
    }while(pasadas <200 && corregido > 0);
    if(pasadas<200)
    {
        cout<<"La red quedo entrenada a :"<<pasadas<<" pasadas"<<endl;
    }
}

/*********************************************************
* La funcion ejecutar() realizara la ejecucion de la neurona con los pesos actuales y devolvera la salida
**********************************************************/
void perceptron::ejecutar()
{
    double potencial;
    potencial = sumatoria_de_pesos(this->cant_entradas, this->pesos, this->entradas);
    this->funcion_activacion(potencial);
    this->funcion_salida();
}
/*********************************************************
* La funcion funcion_activacion(double suma) devolvera el
* valor luego de haber utilizado la funcion de activacion correspondiente
**********************************************************/
void perceptron::funcion_activacion(double suma)
{
    suma = suma - this->umbral;
    switch(this->tipo)
    {
        case 0:
            // Identidad
            this->salida = suma;
            break;
        case 1:
            //  Sigmoidea
            this->salida = 1/(1+ (exp(-1*suma)));
            break;
        case 2:
            // Signo
            if(suma>0.0)
                this->salida = 1;
            else
                this->salida = 0;
            break;
        case 3:
            //Gaussiana
            this->salida = exp(suma*suma);
            break;
        default:
            //Error
            cout<<"La funcion no esta definida se tomara la lineal por defecto";
            this->salida = suma;
    }

}

void perceptron::funcion_salida()
{
    if( this->salida > 0)
    this->salida = 1;
    else
    this->salida = -1;
}
/***************************************************************************
*   Esta funcion corregira los pesos basado en el algoritmo de aprendizaje
*    peso_nuevo = peso_viejo + 2*factor_aprendizaje*( producto de todas las entradas )
*****************************************************************************/
void perceptron::corregir_pesos(double delta_salida)
{
    cout<<"Corrigiendo pesos sinapcticos ....."<<endl;
    int i;
    /*double prod = 1;
    for(i=0;i<this->cant_entradas;i++)
    {
        prod *= this->entradas[i];
    }*/

    for(i = 0; i<this->cant_entradas;i++)
    {
        this->pesos[i] = this->pesos[i] + this->factor_aprendizaje*this->entradas[i]*delta_salida;
        cout<<"Pesos Corregidos : "<<this->pesos[i]<<endl;
    }

    //  Corregir umbral
    this->umbral -= this->factor_aprendizaje*delta_salida;
    cout<<"Umbral corregido: "<< this->umbral<<endl<<endl;

}

/**********************************************************
*    Las funciones guardar y cargar se utilizan para guardar y
*    cargar los pesos de una red entrenada.
*    Tambien se guardaran el umbral y el factor de aprendizaje
*
*    Para guardar los patrones para el entrenamiento utilizaremos la opcion 1
*
*
**********************************************************/
void perceptron::guardar_patrones(char *archivo, int cant_casos, double **entrenamiento)
{
        FILE *f;
        f = fopen(archivo, "w"); //Abre el archivo en modo lectura para cargar los datos
        if(!f)
        {
            perror(archivo);
            cout<<"Error al abrir el archivo "<<archivo<<endl;
            return;
        }
        fprintf(f, "%d\n", cant_casos);
        int j, i;
        for (i = 0; i< cant_casos; i++)
        {
            for(j = 0; j< this->cant_entradas; j++)
            {
                fprintf(f, "%lf  ", entrenamiento[i][j]);
            }
            fprintf(f, "%lf\n", entrenamiento[i][j]);
        }
        fclose(f);

}

double **perceptron::cargar_patrones(char *archivo, int *casos)
{
    FILE *f;
    f = fopen(archivo, "r");
    double **valores = NULL;
    if(!f)
    {
        perror(archivo);
        cout<<"No se puede abrir el archivo: "<<archivo<<endl;
        return valores;
    }
    fscanf(f, "%d\n", casos);
    int i,j, leer;
    leer= this->cant_entradas;
    leer++;
    valores = create_array(*casos, leer);
    for(i = 0; i<*casos; i++)
    {
        for(j=0; j<cant_entradas; j++)
        {
            fscanf(f, "%lf  ", &valores[i][j]);
        }
        fscanf(f, "%lf\n", &valores[i][j]);
    }
    fclose(f);

    return valores;
}

void perceptron::guardar_red(char *archivo, int entr)
{
    FILE *f;
    f = fopen(archivo, "w");
    if(!f)
    {
        cout <<"Error al abrir el archivo!!"<<endl;
        return;
    }
    fprintf(f, "Tipo de red: %d\n", this->tipo);
    fprintf(f, "Cantidad de entradas: %d\n", this->cant_entradas);
    fprintf(f, "Factor de aprendizaje: %lf\n", this->factor_aprendizaje);
    fprintf(f, "Umbral : %lf\n", this->umbral);
    fprintf(f, "Esta entrenada la red? 0 = no, 1 = si : %d\n", entr);
    fprintf(f, "Pesos: \n");
    int i;
    for(i = 0; i<cant_entradas; i++)
    {
        fprintf(f, "%lf ", pesos[i]);
    }
    fclose(f);

}

void perceptron::cargar_red(char *archivo)
{
    FILE *f;
    f = fopen(archivo, "r");
    if(!f)
    {
        perror(archivo);
        cout<<archivo<<endl;
        cout<<"Error al abrir el archivo : "<<archivo;
        return;
    }

    int if_entr;
    while(fscanf(f, "%*s %d", &this->tipo)==0);
    while(fscanf(f, "%*s %d", &this->cant_entradas)==0);
    while(fscanf(f, "%*s %lf", &this->factor_aprendizaje)==0);
    /* Descomentarlo para debug
    cout<< "Tipo de red: "<<this->tipo<<endl;
    cout<<"Cantidad de entradas: "<<this->cant_entradas<<endl;
    cout<<"Factor de aprendizaje: "<< this->factor_aprendizaje<<endl;
    */
    this->entradas = new double[this->cant_entradas];
    this->pesos = new double[this->cant_entradas];
    while(fscanf(f, "%*s %lf", &this->umbral)==0);
    cout<<"Umbral : "<< this->umbral<<endl;
    while(fscanf(f, "%*s %d", &if_entr)==0);
    if(if_entr!=1)
    {
        int i;
        for(i = 0; i<cant_entradas; i++)
        {
            while(fscanf(f, "%*s %lf ", &pesos[i])==0);
        }
    }
    else
    {
        this->iniciar_pesos();
    }


    fclose(f);
}

int perceptron::get_cant_entradas()
{
    return this->cant_entradas;
}
